Skip to content

docs(site): make the llms.txt artifacts discoverable, and fix the landing-card parser - #23336

Open
bloxster wants to merge 5 commits into
mainfrom
docs/llms-txt-discoverability-main
Open

docs(site): make the llms.txt artifacts discoverable, and fix the landing-card parser#23336
bloxster wants to merge 5 commits into
mainfrom
docs/llms-txt-discoverability-main

Conversation

@bloxster

Copy link
Copy Markdown
Collaborator

Why

docs.erigon.tech/llms.txt and /llms-full.txt are published, but nothing points anything at them. They are absent from robots.txt, absent from sitemap.xml, and unlinked from every built HTML page. They are static files under static/, so Docusaurus never routes them and the default sitemap omits them.

The effect is measurable. Asked a single Erigon flag question, ChatGPT read 60 sources — roughly 8 doc pages, ~20 GitHub issue threads, 6 unrelated MCP projects, and Wikipedia's article on HTTP — and touched neither file. We are paying to generate a corpus nothing can find.

What this does

Commit 1 — discoverability.

  • Two <link rel="alternate" type="text/plain"> head tags on every page advertising both .txt URLs.
  • createSitemapItems appends both URLs to the sitemap, preserving every default entry via defaultCreateSitemapItems(rest).
  • A reader-facing section on the MCP page — where someone already asking "how do I point a model at Erigon" will be — plus a one-line pointer from why-using-erigon.

robots.txt is deliberately untouched: no standard directive advertises llms.txt, and the Sitemap: line already there now leads to both files.

The prose lands on mcp.mdx rather than why-using-erigon.mdx because the latter is a card-grid landing page whose body synthesize_landing() replaces wholesale — prose added there would render on the site but never reach llms-full.txt.

Commit 2 — a live corpus bug found while writing the above.

_LANDING_CARD_RE matched a whole card with one pattern: [^<]+ for the title and description text, (?:.*?) for the gaps, under re.DOTALL. [^<]+ cannot cross a <, so a description containing inline markup (<strong>, <code>) fails to match where it stands — and the engine then scans forward through the permissive gap and matches the next card's description and </Link>, swallowing the card in between and pairing a title with the wrong description.

This is live today, on the page whose job is explaining what makes Erigon different. why-using-erigon has 11 cards; llms-full.txt carried 8:

Card What the corpus said
Immutable, Decentralised Data carried Staged Sync's description
Flexible Pruning carried RPC Providers' description
Staged Sync, RPC Providers & Large Stakers, Developers absent entirely

Fixed by parsing in two stages — match each <Link> block first, then find title and description within that block only, which makes a card boundary unrepresentable.

Plus a count guard. This failed silently for as long as it existed because --check only compares generated output against committed output, which makes a systematic generator bug invariant under it: CI stays green while the corpus is wrong. The guard compares parsed cards against lp-card-title occurrences and fails loudly on a mismatch.

A sweep of all 8 card-grid pages confirms why-using-erigon.mdx is the only file affected.

Verification

  • npm ci && npm run build clean.
  • Sitemap: 75 URLs including both .txt; /search still excluded; 73 per-page lastmod preserved.
  • Head tags present on 198/199 pages (the miss is a redirect stub).
  • generate-llms.py --check green; 81 tests pass.
  • The three regression tests were each confirmed to fail against the old parser and pass against the new one.

Note on branches

This is the main counterpart of #23335 (release/3.6). Landing it here means the change is inherited by the next release branch cut from main, with no cutover step: both head tags and both sitemap entries use absolute site-root URLs, so nothing needs rewriting at cut time, and the parser fix plus its regression tests travel with the shared tooling.

docs-deploy.yml currently exists only on release/3.5, so neither this nor #23335 changes the live site until the 3.6 deploy switchover lands.

Bloxster and others added 2 commits August 17, 2026 10:59
llms.txt and llms-full.txt are published but nothing points at them: they
are static files, so Docusaurus never routes them, the default sitemap
omits them, and no page links to them. A crawler or agent can only reach
them by guessing the path, which in practice means they are never found —
a browsing model asked one flag question read ~20 GitHub issue threads
instead, none of which are authoritative.

Advertise them three ways:

- two <link rel="alternate" type="text/plain"> head tags, so every page
  declares where the machine-readable copies live
- createSitemapItems, appending both URLs to the generated sitemap. The
  sibling ignorePatterns/lastmod options are closure-bound inside
  defaultCreateSitemapItems, so appended items are neither filtered nor
  double-processed and /search stays excluded
- a reader-facing section on the MCP page, with a pointer from
  "Why using Erigon?"

The section goes on the MCP page rather than "Why using Erigon?" because
the latter is a card-grid landing page, whose body generate-llms.py
replaces with synthesized bullets — prose added there would render on the
site but never reach llms-full.txt.

robots.txt is left alone: there is no standard directive for advertising
llms.txt, and the Sitemap: line already there now leads to both files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dx34ND1m4ySTJXqMR8kRDX
_LANDING_CARD_RE matched a whole card with one pattern: `[^<]+` for the
title and description text, `(?:.*?)` for the gaps, under re.DOTALL.
`[^<]+` cannot cross a `<`, so a description containing inline markup
(<strong>, <code>) fails to match where it stands — and the engine then
scans forward through the permissive gap and matches the *next* card's
description and </Link>, swallowing the card in between and pairing a
title with the wrong description.

This is live in the published corpus, on the page whose job is
explaining what makes Erigon different. why-using-erigon has 11 cards;
llms-full.txt carried 8:

  Immutable, Decentralised Data  <- Staged Sync's description
  Flexible Pruning               <- RPC Providers' description
  Staged Sync, RPC Providers & Large Stakers, Developers  <- absent

Parse in two stages instead: match each <Link> block first, then find
the title and description within that block only. A card boundary is
then unrepresentable, so no match can cross one.

Add a count guard. This failed silently for as long as it existed
because `--check` only compares generated output against committed
output, which makes a systematic generator bug invariant under it: CI
stays green while the corpus is wrong. The guard compares parsed cards
against lp-card-title occurrences and fails loudly on a mismatch.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves discovery of Erigon’s LLM documentation artifacts and fixes landing-card extraction.

Changes:

  • Advertises artifacts through page metadata, sitemap entries, and documentation links.
  • Parses landing cards within individual <Link> boundaries.
  • Adds regression tests and regenerates the full corpus.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
llms-full.txt Updates the repository corpus.
docs/site/static/llms-full.txt Updates the deployed corpus.
docs/site/scripts/test_generate_llms.py Adds parser regression tests.
docs/site/scripts/generate-llms.py Fixes card parsing and adds validation.
docs/site/docusaurus.config.ts Adds head and sitemap discovery.
docs/site/docs/get-started/why-using-erigon.mdx Links to LLM artifacts.
docs/site/docs/fundamentals/mcp.mdx Documents artifact usage.
Suppressed comments (1)

docs/site/docusaurus.config.ts:177

  • The MCP page now links both artifacts, so “Nothing else on the web links to them” is inaccurate. The relevant rationale is that static files are omitted from Docusaurus's default sitemap.
          // The llms.txt artifacts live in static/, so Docusaurus never routes
          // them and the default sitemap omits them. Nothing else on the web
          // links to them either, which leaves them unindexable and unreachable
          // by search — append them explicitly.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/site/scripts/generate-llms.py Outdated
Comment thread docs/site/docs/get-started/why-using-erigon.mdx Outdated
Comment thread docs/site/docusaurus.config.ts Outdated

@yperbasis yperbasis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for three correctness issues:

  1. docs/site/docusaurus.config.ts: advertise /llms.txt with rel="describedby". The llms.txt v2 proposal reserves rel="alternate" type="text/markdown" for a page-specific Markdown representation and defines rel="describedby" for the llms.txt file covering a page: https://llmstxt.org/#proposal. These site-wide aggregate files are not alternate representations of every page, and v2-aware agents may specifically look for describedby. Keep llms-full.txt discoverable through llms.txt, the visible docs, and the sitemap instead of declaring it a page-wide alternate.

  2. docs/site/scripts/generate-llms.py: the mismatch guard is bypassed when every card fails extraction. if not cards: return None runs before expected is computed, so collect_pages silently falls back to strip_mdx. Compute expected before the early return and add an all-malformed-grid regression test. This matches the existing unresolved thread: #23336 (comment).

  3. docs/site/docs/fundamentals/mcp.mdx: llms-full.txt is advertised as containing every documentation page in full, but collect_pages replaces the complete body of every card-grid page with the synthesized card list. For example, the generated Why using Erigon entry omits its introduction, benefits prose, and MCP section. Preserve the non-card prose or describe this as a cleaned and synthesized corpus instead of claiming complete page contents.

Reviewed at eee030a9f1c37e951048dc8420c5af6d61fcce63. The 81 documentation-script tests, artifact check, diff check, and GitHub docs build are green.

@AskAlexSharov
AskAlexSharov disabled auto-merge August 17, 2026 11:39
… claims

Review feedback from @yperbasis and Copilot on #23336. Five fixes.

1. Advertise llms.txt with rel="describedby", not rel="alternate".
   The llmstxt.org proposal defines describedby for the llms.txt file that
   covers a page, and reserves alternate + text/markdown for a *per-page*
   Markdown representation. A site-wide index is not an alternate
   representation of every page, and v2-aware agents look for describedby.

   llms-full.txt is no longer advertised in head at all: it describes no
   single page. It stays discoverable through llms.txt, the sitemap, and
   the MCP docs page.

2. Close a hole in the card-count guard. `if not cards: return None` ran
   before the count was taken, so a grid where *every* card failed to
   parse was indistinguishable from an ordinary prose page: the caller
   fell back to strip_mdx and the guard never ran — silently degrading
   the exact case it exists to catch. Count first, parse second, and
   return None only when the page has no cards at all.

3. Stop claiming llms-full.txt holds "every documentation page, in full".
   It does not: synthesize_landing replaces the whole body of a card-grid
   page with its card list, so why-using-erigon loses its introduction and
   prose. Describe the corpus as cleaned rather than verbatim, and say
   what is dropped.

4. Stop grouping llms.txt with llms-full.txt as "the whole documentation
   as one plain-text file" on why-using-erigon. llms.txt is only an index.

5. Drop "nothing else links to them" from the config comment — this PR
   adds the MCP page links, which makes it false.

Also refresh the stated file size, 420 KB -> 430 KB.
@bloxster

Copy link
Copy Markdown
Collaborator Author

@yperbasis all three addressed in 1dd741e, rebased onto your merge of main.

1. rel="describedby" — you're right, and I checked the proposal rather than taking it on trust: it defines describedby for the llms.txt file covering a page and reserves alternate + text/markdown for a per-page Markdown representation. A site-wide index is not an alternate representation of every page. Switched to describedby, and dropped the llms-full.txt head tag entirely — it describes no single page, so it has no business in a per-page relation. It stays reachable via llms.txt, the sitemap, and the MCP page.

2. Guard hole — confirmed before fixing. With every card malformed, cards was empty, return None fired before the count, and collect_pages fell back to strip_mdx: total parser failure was indistinguishable from a prose page, which is precisely what the guard was meant to catch. Now counts first, parses second, and returns None only when the page has no cards. New test verified to fail against the old ordering.

3. "Every documentation page, in full" — confirmed and materially false: why-using-erigon.mdx is 183 source lines and reaches the corpus as its 11-bullet card list alone, losing the introduction, the prose and the MCP section. The table now says "the text of every documentation page", with an explicit sentence that the corpus is cleaned rather than verbatim and that card-grid index pages are reduced to their links.

Verified after the rebase: --check green, 82 tests pass, build clean, describedby on 198/199 pages, zero alternate/text/plain tags and zero llms-full head tags remaining, sitemap still carries both files.

One thing worth flagging: #23335 already merged into release/3.6 with all three defects. I'll open a follow-up there carrying these same fixes.

@bloxster

Copy link
Copy Markdown
Collaborator Author

Follow-up for release/3.6 opened: #23341 — same three fixes, since #23335 merged before this review landed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (1)

docs/site/docusaurus.config.ts:100

  • The PR description still promises two rel="alternate" type="text/plain" head tags on every page, while this now emits one describedby link and deliberately omits llms-full.txt. Please either restore the advertised tags or update the description and verification so they match the shipped discovery contract.
          rel: 'describedby',
          href: 'https://docs.erigon.tech/llms.txt',

@yperbasis yperbasis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for three remaining correctness issues:

  1. The landing-card mismatch guard still misses cards whose title marker disappears.
  2. The advertised llms.txt index does not directly expose llms-full.txt.
  3. The new completeness wording ignores the published archived documentation versions.

Reviewed at 1dd741e8c1. Targeted validation is green: 82 documentation tests and the llms artifact drift check pass.

Comment thread docs/site/scripts/generate-llms.py Outdated
Comment thread docs/site/docusaurus.config.ts
Comment thread docs/site/docs/fundamentals/mcp.mdx Outdated
…, scope

Follow-up review from @yperbasis on #23336. Three findings, all confirmed
by reproduction before fixing.

1. The guard counted the marker it was validating. `expected` came from
   `lp-card-title`, so if a card lost or renamed that marker the count
   shrank in step with the loss it was meant to detect: verified that a
   two-card grid with one renamed marker emitted one bullet and raised
   nothing. If every marker changed, `expected` hit zero and the page
   fell back to strip_mdx. Count `lp-card` containers instead — the
   wrapper is not consumed by the parse, so the two signals stay
   independent. Attributes are now matched order-independently and `to=`
   is read separately, which the container match no longer pins down.

2. llms.txt had no route to llms-full.txt. Neither committed index
   contained the string at all, so once the llms-full head tag was
   removed, an agent following rel="describedby" reached an index with no
   way to find the full corpus. The generator now emits that link, and
   both copies are regenerated.

3. "Every documentation page" was still wrong. SECTIONS scans only docs/
   and help-center/, while the site also publishes v3.3 and v3.4 from
   versioned_docs/ — neither artifact contains those URLs. Say current
   documentation, and state the exclusion outright.

Two new tests: a renamed title marker must raise rather than be absorbed
(verified to fail against the previous count), and card attributes must
parse in either order.
bloxster pushed a commit that referenced this pull request Aug 17, 2026
…, scope

Follow-up review from @yperbasis on #23336. Three findings, all confirmed
by reproduction before fixing.

1. The guard counted the marker it was validating. `expected` came from
   `lp-card-title`, so if a card lost or renamed that marker the count
   shrank in step with the loss it was meant to detect: verified that a
   two-card grid with one renamed marker emitted one bullet and raised
   nothing. If every marker changed, `expected` hit zero and the page
   fell back to strip_mdx. Count `lp-card` containers instead — the
   wrapper is not consumed by the parse, so the two signals stay
   independent. Attributes are now matched order-independently and `to=`
   is read separately, which the container match no longer pins down.

2. llms.txt had no route to llms-full.txt. Neither committed index
   contained the string at all, so once the llms-full head tag was
   removed, an agent following rel="describedby" reached an index with no
   way to find the full corpus. The generator now emits that link, and
   both copies are regenerated.

3. "Every documentation page" was still wrong. SECTIONS scans only docs/
   and help-center/, while the site also publishes v3.3 and v3.4 from
   versioned_docs/ — neither artifact contains those URLs. Say current
   documentation, and state the exclusion outright.

Two new tests: a renamed title marker must raise rather than be absorbed
(verified to fail against the previous count), and card attributes must
parse in either order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants